package com.mybatis.jpa.sqlbuilder.insert;

import com.mybatis.jpa.utils.FieldFacade;
import com.mybatis.jpa.utils.ObjectFacade;
import javassist.*;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 动态创建类并通过classlaoder动态加载，被创建的类主要功能是生成sql片段,被创建的类内容如下：
 * 
 * public class 类名 implements SqlWriter {

	public <T>List<String> writer(T object, int index) {
		List<String> list = new ArrayList<>();
		ApiOutWmsSaleOrderHeader header = (ApiOutWmsSaleOrderHeader) object;
		
		if (StringUtils.isBlank(header.getAddLocalTime())) {
			list.add("''");
		} else {
			list.add(String.format(" #{%s[%s].%s} ", ParameterConstants.PARAM, index, "addLocalTime"));
		}
		
			return list;
		}
	}
 *	
 *  javassist基本上不支持基本类型对象，必须用包装类代替基本类型
 *  
 * @author K
 *
 */
public class JavassistInsertSqlWriterCreator {
	
	private static final String LIST_CLASS_NAME = List.class.getName();
	private static final String STRINGUTILS_CLASS_NAME = StringUtils.class.getName();
	private static final String SQLWRITER_CLASS_NAME = SqlWriter.class.getName();
	private static final String OBJECT_CLASS_NAME = Object.class.getName();
	private static final String INT_CLASS_NAME = int.class.getName();
	private static final String TARGET_VARIABLE_NAME = "target";
	private static final String LIST_VARIABLE_NAME = "list";
	
	private static ClassLoader classLoader = JavassistInsertSqlWriterCreator.class.getClassLoader();
	private static ClassPool classPool = ClassPool.getDefault();
	private CtClass ctClass; 
	
	private String facadeSourceObjectClassName;
	// sql占位符别名
	private String sqlPlaceholderAlias;
		
	private StringBuilder sb = new StringBuilder(1000);
	
	static {
		/*
		 * 给ClassPool添加当前类的类加载器，不使用默认的当前线程类加载器。
		 * 因为在web容器，例如tomcat，会自定义类加载器去加载class，
		 * ClassPool使用当前线程类加载器是无法调用其他类加载器的对象
		 */
		classPool.insertClassPath( new LoaderClassPath(classLoader));
	}
					
	public SqlWriter create(ObjectFacade objectFacade, String sqlPlaceholderAlias) throws Exception {
		this.sqlPlaceholderAlias = sqlPlaceholderAlias;
		// 定义对象
		createClass(objectFacade);
		createMethod(objectFacade);
		return genObject();
	}
	
	private void createClass(ObjectFacade objectFacade) throws Exception {
		facadeSourceObjectClassName = objectFacade.getOriginalClass().getName();
		// 定义类名
		String dynamicClassName = "Javassist_" + IncrementSeed.incrementAndGet() + "_" + objectFacade.getOriginalClass().getSimpleName();
		
		CtClass sqlWriter = classPool.get(SQLWRITER_CLASS_NAME);
		ctClass = classPool.makeClass(dynamicClassName);
		// 定义类要实现的接口
		ctClass.addInterface(sqlWriter);
	}
	
	private void createMethod(ObjectFacade objectFacade) throws Exception {
		CtClass returnType = classPool.get(LIST_CLASS_NAME);
		CtClass objectType = classPool.get(OBJECT_CLASS_NAME);
		CtClass intType = classPool.get(INT_CLASS_NAME);
		String methodName = "writer";
		String body = genBody(objectFacade);
		CtMethod ctMethod = CtNewMethod.make(returnType, methodName, new CtClass[]{objectType, intType}, null, body, ctClass);
		ctClass.addMethod(ctMethod);
	}
	
	private String genBody(ObjectFacade objectFacade) throws Exception {
		leftBrace();
		defineVariables();
		createLogic(objectFacade);
		rightBrace();
		return sb.toString();
	}
	
	private void defineVariables() {
		sb.append(LIST_CLASS_NAME).append(" ").append(LIST_VARIABLE_NAME).append(" = new ").append(ArrayList.class.getName()).append("();");
		sb.append(facadeSourceObjectClassName).append(" ").append(TARGET_VARIABLE_NAME).append(" = (")
				.append(facadeSourceObjectClassName).append(')').append("$1;");
	}
	
	private void createLogic(ObjectFacade objectFacade) throws Exception {
		List<FieldFacade> fieldFacades = objectFacade.getFieldFacades();
		List<String> columnNames = objectFacade.getColumnNames();

		int length = fieldFacades.size();
		for (int i = 0; i < length; i++) {
			FieldFacade fieldFacade = fieldFacades.get(i);
			// 判断字段名是否被过滤，过滤的字段不需要生成逻辑，过滤动作在InsertSqlBuilder类filter方法操作
			if (!columnNames.contains(fieldFacade.getColumnName())) {
				continue;
			}
			
			String type = fieldFacade.getJavaType().getName();

			if (fieldFacade.isId()) {
				if (JavaTypeCollections.isNumberType(type)) {
					numberTypeOperation(fieldFacade);
				} else if (JavaTypeCollections.isNumberBasicType(type)) {
					numberBasicTypeOperation(fieldFacade);
				} else {
					listAddValue(fieldFacade);
				}
				continue;
			}
			
			if (JavaTypeCollections.isStringType(type) /*&& !isNullable*/) {
				stringTypeOperation(fieldFacade) ;
			} else if (JavaTypeCollections.isNumberType(type) /*&& !isNullable*/) {
				numberTypeOperation(fieldFacade);
			} else if (JavaTypeCollections.isDateType(type) /*&& !isNullable*/) {
				dateTypeOperation(fieldFacade);
			} else {
				listAddValue(fieldFacade);
			}
		}
		returnResult();
	}
	
	// 数字包装类型的if else判断逻辑
	private void numberTypeOperation(FieldFacade fieldFacade) throws Exception {
		createIfNumberLogic(fieldFacade);
		createElse(fieldFacade);
	}
	
	// 数字包装类的
	private void createIfNumberLogic(FieldFacade fieldFacade) throws Exception {
		String typeName = fieldFacade.getJavaType().getName();
		String methodName = genMethodName(fieldFacade.getFieldName());
		// 包装类比较要小心，必须转换成包装类比较，不能包装类和基本类型进行比较
		sb.append("if(").append(TARGET_VARIABLE_NAME).append('.').append(methodName).append("() == null || ")
				.append(TARGET_VARIABLE_NAME).append('.').append(methodName).append("() == ").append(typeName)
				.append("#valueOf(\"0\"))");
		leftBrace();
		listAddNumberValue(fieldFacade);
		rightBrace();
	}
	
	private void listAddNumberValue(FieldFacade fieldFacade) throws Exception {
		// 属性字段是对应数据库主键并且属性值是零，插入null
		if (fieldFacade.isId() && fieldFacade.isNumEqNullOrEqZero()) {
			listAddStringValue("\"null\"");
		} else {
			listAddStringValue("new Integer(0)");
		}
	}
	
	private void listAddStringValue(String value) {
		sb.append(LIST_VARIABLE_NAME).append(".add(").append(value).append(");");
	}
	
	// else逻辑
	private void createElse(FieldFacade fieldFacade) {
		sb.append(" else ");
		leftBrace();
		listAddValue(fieldFacade);
		rightBrace();
	}
	
	// 数字基本类型的if else判断逻辑
	private void numberBasicTypeOperation(FieldFacade fieldFacade) throws Exception {
		createIfNumberBasicCondition(fieldFacade);
		createElse(fieldFacade);
	}
	
	// 数字基本类型判断条件
	private void createIfNumberBasicCondition(FieldFacade fieldFacade) throws Exception {
		String methodName = genMethodName(fieldFacade.getFieldName());
		sb.append("if(").append(TARGET_VARIABLE_NAME).append('.').append(methodName).append("() == 0)");
		leftBrace();
		listAddNumberValue(fieldFacade);
		rightBrace();
	}
	
	private void listAddValue(FieldFacade fieldFacade) {
		// 注意，如果调用静态方法时必须用#分隔，例如String.format --> String#format
		// 如果方法出入是参数是可变传入，必须传入Object数组
		// $2代表方法参数列表里第二个参数，第二，第三个参数如此类推，$3,$4
		sb.append(LIST_VARIABLE_NAME);
		sb.append(".add(String#format(\" #{%s[%s].%s} \", new Object[]{");
		sb.append("\"").append(sqlPlaceholderAlias).append("\",");
		sb.append("Integer.valueOf($2)").append(",");
		sb.append("\"").append(fieldFacade.getFieldName()).append("\"}));");
	}
	
	// 属性类型是String的if else判断逻辑
	private void stringTypeOperation(FieldFacade fieldFacade) {
		createIfStringCondition(fieldFacade);
		createElse(fieldFacade);
	}
	
	// String类型if判断条件
	private void createIfStringCondition(FieldFacade fieldFacade) {
		String methodName = genMethodName(fieldFacade.getFieldName());
		// 注意，空字节必须用包装类初始化
		sb.append("if (").append(STRINGUTILS_CLASS_NAME).append("#isBlank(").append(TARGET_VARIABLE_NAME).append('.')
				.append(methodName).append("()").append("))");
		leftBrace();
		listAddStringValue("new String(\"''\")");
		rightBrace();
	}
	
	// 日期类型的if else判断逻辑
	private void dateTypeOperation(FieldFacade fieldFacade) {
		createIfDateCondition(fieldFacade);
		createElse(fieldFacade);
	}
	
	private void createIfDateCondition(FieldFacade fieldFacade) {
		String methodName = genMethodName(fieldFacade.getFieldName());
		
		sb.append("if (").append(TARGET_VARIABLE_NAME).append('.').append(methodName).append("() == null").append(')');
		leftBrace();
		listAddStringValue("\"now()\"");
		rightBrace();
	}
	
	private void returnResult() {
		sb.append(" return list; ");
	}

	private String genMethodName(String fieldName) {
		return "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
	}

	private void leftBrace() {
		sb.append('{');
	}
	
	private void rightBrace() {
		sb.append('}');
	}
	
	private SqlWriter genObject() throws Exception {
		// 使用当前类的类加载器实例化对象
		SqlWriter sqlWriter = (SqlWriter) ctClass.toClass(classLoader, null).newInstance();
		// 从ClassPool中移除该CtClass，因为该对象执行一次就不会再次调用。
		// 不移除该对象一直在ClassPool，无法进行给JVM回收，导致内存溢出
		ctClass.detach(); 
		return sqlWriter;
	}
	
	/**
	 * 生成的字节码输出
	 * @param objectFacade 生成字节码的对象 
	 * @param path 保存路径
	 * @throws Exception
	 */
	public void outputClass(ObjectFacade objectFacade, String path) throws Exception {
		createClass(objectFacade);
		createMethod(objectFacade);
		ctClass.writeFile(path);
	}
}
